package com.yan

import java.lang.instrument.Instrumentation
import java.nio.file.Files
import java.nio.file.Paths
import java.util.logging.Logger


val logger: Logger = Logger.getLogger("agent")
fun agentmain(args: String, instrumentation: Instrumentation) {
    agent(args, instrumentation)
}

fun premain(args: String, instrumentation: Instrumentation) {
    agent(args, instrumentation)
}

fun agent(args: String, instrumentation: Instrumentation) {
    // ①当前已加载的所有类
    val allLoadedClasses = instrumentation.allLoadedClasses
    // ②通过自定义的过滤规则得到的需要替换的类
    val targetClasses = findTargetClasses(args, allLoadedClasses)
    if (targetClasses.isEmpty()) {
        println("no class found")
        return
    }
    // ③添加类转换规则的具体实现
    instrumentation.addTransformer({ loader, className, classBeingRedefined, protectionDomain, classfileBuffer ->
        if (!classFilter(args, classBeingRedefined)) {
            return@addTransformer classfileBuffer
        }
        val agentClassPath = Paths.get(args, "$className.class")
        logger.info("start redefine class: $agentClassPath")
        Files.newInputStream(agentClassPath).use {
            return@addTransformer it.readBytes()
        }
    }, true)
    // ④更新目标类到目标JVM
    println(targetClasses.map { it.name })
    instrumentation.retransformClasses(*targetClasses)
}

// 定义了哪些类时需要替换的， 一般需要从已加载的类中进行过滤
// 方式有很多， 比如扫描/tmp/classes路径下的新类， 能找到则表示要替换， 或者通过args来指定也行
fun findTargetClasses(args: String, allLoadedClasses: Array<Class<Any>>): Array<Class<Any>> {
   return allLoadedClasses.filter { classFilter(args, it) }.toTypedArray()
}

fun classFilter(args: String, target: Class<*>): Boolean {
    val name = target.name
    val path = Paths.get(args, "${name.replace('.', '/')}.class")
    return (name.startsWith("com.yan") // 只针对业务类， 一般使用包名的前缀进行过滤
            && Files.exists(path)) // 查看指定路径下有没有相关的类文件
}

